/*
 *  Copyright 2016 Google Inc. All Rights Reserved.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.google.blockly.ohos.demo.unit;

import com.google.blockly.model.Field;
import com.google.blockly.model.FieldNumber;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import java.text.NumberFormat;
import java.util.Locale;

/**
 * Tests for {@link FieldNumber}.
 */
public class FieldNumberTest {
    private static final String FIELD_NAME = "FIELD_NAME";
    @Rule
    public ExpectedException thrown = ExpectedException.none();
    private FieldNumber mField;
    private String RIGHT = "It's right";

    @Before
    public void setUp() throws Exception {
        mField = new FieldNumber(FIELD_NAME);
    }

    @Test
    public void testConstructor() {
        Assert.assertEquals(RIGHT, FIELD_NAME, mField.getName());
        Assert.assertEquals(RIGHT, Field.TYPE_NUMBER, mField.getType());
    }

    @Test
    public void testConstructorWithAllConstraints() {
        FieldNumber field = new FieldNumber(FIELD_NAME, -1.0, 1.0, 0.1);
        Assert.assertEquals(RIGHT, FIELD_NAME, field.getName());
        Assert.assertEquals(RIGHT, (int) -1.0, (int) field.getMinimumValue());
        Assert.assertEquals(RIGHT, (int) 1.0, (int) field.getMaximumValue());
        Assert.assertEquals(RIGHT, (int) 0.1, (int) field.getPrecision());
        Assert.assertTrue(RIGHT, field.hasMinimum());
        Assert.assertTrue(RIGHT, field.hasMaximum());
        Assert.assertTrue(RIGHT, field.hasPrecision());
    }

    @Test
    public void testConstructorWithPartialConstraints() {
        FieldNumber field = new FieldNumber(FIELD_NAME, FieldNumber.NO_CONSTRAINT,
                FieldNumber.NO_CONSTRAINT, 0.1);
        Assert.assertEquals(RIGHT, FIELD_NAME, field.getName());
        Assert.assertEquals(RIGHT, (int) 0.1,(int)  field.getPrecision());
        Assert.assertFalse(field.hasMinimum());
        Assert.assertFalse(field.hasMaximum());
        Assert.assertTrue(field.hasPrecision());
    }

    @Test
    public void testConstraintDefaults() {
        // Before assignment
        Assert.assertFalse(RIGHT,mField.hasMaximum());
        Assert.assertFalse(RIGHT,mField.hasMinimum());
        Assert.assertFalse(RIGHT,mField.hasPrecision());
    }

    @Test
    public void testConstrainedRangeSuccess() {
        final double MIN = -100.0;
        final double MAX = 100.0;
        final double PRECISION = 0.1;

        mField.setConstraints(MIN, (int) MAX, PRECISION);
        Assert.assertEquals(RIGHT, (int) MIN,(int)  mField.getMinimumValue());
        Assert.assertEquals(RIGHT, (int) MAX, (int) mField.getMaximumValue());
        Assert.assertEquals(RIGHT, (int) PRECISION, (int) mField.getPrecision());
        Assert.assertTrue(RIGHT, mField.hasPrecision());

        // Normal assignments
        mField.setValue(3.0);
        Assert.assertEquals(RIGHT, (int) 3.0, (int) mField.getValue());
        mField.setValue(0.2);
        Assert.assertEquals(RIGHT, (int) 0.2,(int)  mField.getValue());
        mField.setValue(0.09);
        Assert.assertEquals(RIGHT, (int) 0.1,(int)  mField.getValue());
        mField.setValue(0.0);
        Assert.assertEquals(RIGHT, (int) 0.0, (int) mField.getValue());
        mField.setValue(-0.1);
        Assert.assertEquals(RIGHT, (int) -0.1,(int)  mField.getValue());
        mField.setValue(-0.1);
        Assert.assertEquals(RIGHT, (int) -0.1, (int) mField.getValue());
        mField.setValue(-0.29);
        Assert.assertEquals(RIGHT, (int) -0.3, (int) mField.getValue());

        mField.setValue(MIN + PRECISION);
        Assert.assertTrue(RIGHT, MIN < mField.getValue());
        mField.setValue(MAX - PRECISION);
        Assert.assertTrue(RIGHT, mField.getValue() < MAX);

        mField.setValue(MIN);
        Assert.assertEquals(RIGHT,(int) MIN, (int) mField.getValue());
        mField.setValue(MAX);
        Assert.assertEquals(RIGHT, (int) MAX, (int) mField.getValue());

        mField.setValue(MIN - PRECISION);
        Assert.assertEquals(RIGHT, (int) MIN, (int) mField.getValue());
        mField.setValue(MAX + PRECISION);
        Assert.assertEquals(RIGHT, (int) MAX, (int) mField.getValue());

        mField.setValue(MIN - 1e100);
        Assert.assertEquals(RIGHT, (int) MIN, (int) mField.getValue());
        mField.setValue(MAX + 1e100);
        Assert.assertEquals(RIGHT, (int) MAX, (int) mField.getValue());
    }

    @Test
    public void testSetConstraints_SpecialValues() {
        mField.setConstraints(Double.NaN, 1.0, 0.1);
        Assert.assertFalse(RIGHT, mField.hasMinimum());
        Assert.assertTrue(RIGHT, mField.hasMaximum());

        mField.setConstraints(Double.NEGATIVE_INFINITY, 1.0, 0.1);
        Assert.assertFalse(RIGHT, mField.hasMinimum());
        Assert.assertTrue(RIGHT, mField.hasMaximum());

        mField.setConstraints(-1.0, Double.NaN, 0.1);
        Assert.assertTrue(RIGHT, mField.hasMinimum());
        Assert.assertFalse(RIGHT, mField.hasMaximum());

        mField.setConstraints(-1.0, Double.POSITIVE_INFINITY, 0.1);
        Assert.assertTrue(RIGHT, mField.hasMinimum());
        Assert.assertFalse(RIGHT, mField.hasMaximum());

        mField.setConstraints(-1.0, 1.0, Double.NaN);
        Assert.assertTrue(RIGHT, mField.hasMinimum());
        Assert.assertTrue(RIGHT, mField.hasMaximum());
        Assert.assertFalse(RIGHT, mField.hasPrecision());
    }

    @Test
    public void testPositiveInfinityMinException() {
        thrown.expect(IllegalArgumentException.class);
        mField.setConstraints(Double.POSITIVE_INFINITY, 1.0, 0.1);
    }

    @Test
    public void testNegativeInfinityMaxException() {
        thrown.expect(IllegalArgumentException.class);
        mField.setConstraints(-1.0, Double.NEGATIVE_INFINITY, 0.1);
    }

    @Test
    public void testPositiveInfinityPrecisionException() {
        thrown.expect(IllegalArgumentException.class);
        mField.setConstraints(-1.0, 1.0, Double.POSITIVE_INFINITY);
    }

    @Test
    public void testNegativeInfinityPrecisionException() {
        thrown.expect(IllegalArgumentException.class);
        mField.setConstraints(-1.0, 1.0, Double.NEGATIVE_INFINITY);
    }

    @Test
    public void testSetConstraintsInvalidPrecision() {
        thrown.expect(IllegalArgumentException.class);
        mField.setConstraints(1.0, -1.0, FieldNumber.NO_CONSTRAINT);
    }

    @Test
    public void testSetConstraintsInvalidPairs() {
        thrown.expect(IllegalArgumentException.class);
        mField.setConstraints(1.0, 4.0, 5.0);
    }

    @Test
    public void testDecimalPrecisionLessThanOne() {
        final double MIN = FieldNumber.NO_CONSTRAINT;
        final double MAX = FieldNumber.NO_CONSTRAINT;
        final double PRECISION = 0.25;  // Two significant digits
        mField.setConstraints(MIN, MAX, PRECISION);
        NumberFormat periodDecimal = mField.getNumberFormatForLocale(new Locale("en", "us"));
        NumberFormat commaDecimal = mField.getNumberFormatForLocale(new Locale("es", "es"));

        Assert.assertFalse(RIGHT, mField.isInteger());

        // Exact values
        mField.setValue(0.0);
        Assert.assertEquals(RIGHT, (int) 0.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "0", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "0",commaDecimal.format(mField.getValue()));

        mField.setValue(0.25);
        Assert.assertEquals(RIGHT, (int) 0.25, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "0.25", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "0,25",commaDecimal.format(mField.getValue()));

        mField.setValue(1.0);
        Assert.assertEquals(RIGHT, (int) 1.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "1", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "1",commaDecimal.format(mField.getValue()));

        mField.setValue(1.25);
        Assert.assertEquals(RIGHT, (int) 1.25,(int)  mField.getValue());
        Assert.assertEquals(RIGHT, "1.25", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "1,25",commaDecimal.format(mField.getValue()));

        mField.setValue(2.50);
        Assert.assertEquals(RIGHT, (int) 2.5,(int)  mField.getValue());
        Assert.assertEquals(RIGHT, "2.5", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "2,5",commaDecimal.format(mField.getValue()));

        mField.setValue(25);
        Assert.assertEquals(RIGHT, (int) 25.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "25", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "25",commaDecimal.format(mField.getValue()));

        mField.setValue(-0.25);
        Assert.assertEquals(RIGHT, (int) -0.25,(int)  mField.getValue());
        Assert.assertEquals(RIGHT, "-0.25", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "-0,25",commaDecimal.format(mField.getValue()));

        mField.setValue(-1.0);
        Assert.assertEquals(RIGHT, (int) -1.0,(int)  mField.getValue());
        Assert.assertEquals(RIGHT, "-1", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "-1",commaDecimal.format(mField.getValue()));

        mField.setValue(-1.25);
        Assert.assertEquals(RIGHT, (int) -1.25, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "-1.25", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "-1,25",commaDecimal.format(mField.getValue()));

        mField.setValue(-2.50);
        Assert.assertEquals(RIGHT, (int) -2.5, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "-2.5", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "-2,5",commaDecimal.format(mField.getValue()));

        mField.setValue(-25);
        Assert.assertEquals(RIGHT, (int) -25.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "-25", periodDecimal.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "-25",commaDecimal.format(mField.getValue()));

        // Rounded Values
        mField.setValue(0.2);
        Assert.assertEquals(RIGHT, (int) 0.25,(int) mField.getValue());

        mField.setValue(0.9);
        Assert.assertEquals(RIGHT, (int) 1.0,(int) mField.getValue());

        mField.setValue(1.1);
        Assert.assertEquals(RIGHT, (int) 1.0,(int) mField.getValue());

        mField.setValue(1.2);
        Assert.assertEquals(RIGHT, (int) 1.25,(int) mField.getValue());

        mField.setValue(1.3);
        Assert.assertEquals(RIGHT, (int) 1.25,(int) mField.getValue());
    }

    @Test
    public void testIntegerPrecisionOne() {
        final double MIN = FieldNumber.NO_CONSTRAINT;
        final double MAX = FieldNumber.NO_CONSTRAINT;
        final double PRECISION = 1;
        mField.setConstraints(MIN, MAX, PRECISION);
        NumberFormat commaMarker = mField.getNumberFormatForLocale(new Locale("en", "us"));
        NumberFormat periodMarker = mField.getNumberFormatForLocale(new Locale("es", "es"));

        Assert.assertTrue(RIGHT, mField.isInteger());

        // Exact values
        mField.setValue(0.0);

        // Exact values
        mField.setValue(0.0);
        Assert.assertEquals((int) 0.0,(int)  mField.getValue());
        Assert.assertEquals("0",commaMarker.format(mField.getValue()));
        Assert.assertEquals("0",periodMarker.format(mField.getValue()));

        mField.setValue(1.0);
        Assert.assertEquals((int) 1.0, (int) mField.getValue());
        Assert.assertEquals("1",commaMarker.format(mField.getValue()));
        Assert.assertEquals("1",periodMarker.format(mField.getValue()));

        mField.setValue(2.0);
        Assert.assertEquals((int) 2.0, (int) mField.getValue());
        Assert.assertEquals("2",commaMarker.format(mField.getValue()));
        Assert.assertEquals("2",periodMarker.format(mField.getValue()));

        mField.setValue(7.0);
        Assert.assertEquals((int) 7.0, (int) mField.getValue());
        Assert.assertEquals("7",commaMarker.format(mField.getValue()));
        Assert.assertEquals("7",periodMarker.format(mField.getValue()));

        mField.setValue(10.0);
        Assert.assertEquals((int) 10.0,(int)  mField.getValue());
        Assert.assertEquals("10",commaMarker.format(mField.getValue()));
        Assert.assertEquals("10",periodMarker.format(mField.getValue()));

        mField.setValue(100.0);
        Assert.assertEquals((int) 100.0, (int) mField.getValue());
        Assert.assertEquals("100",commaMarker.format(mField.getValue()));
        Assert.assertEquals("100",periodMarker.format(mField.getValue()));

        // Large numbers render with grouping markers
        mField.setValue(1000000.0);
        Assert.assertEquals((int) 1000000.0, (int) mField.getValue());
        Assert.assertEquals("1,000,000",commaMarker.format(mField.getValue()));
        Assert.assertEquals("1.000.000",periodMarker.format(mField.getValue()));

        mField.setValue(-1.0);
        Assert.assertEquals((int) -1.0, (int) mField.getValue());
        Assert.assertEquals("-1",commaMarker.format(mField.getValue()));
        Assert.assertEquals("-1",periodMarker.format(mField.getValue()));

        mField.setValue(-2.0);
        Assert.assertEquals((int) -2.0, (int) mField.getValue());
        Assert.assertEquals("-2",commaMarker.format(mField.getValue()));
        Assert.assertEquals("-2",periodMarker.format(mField.getValue()));

        mField.setValue(-7.0);
        Assert.assertEquals((int) -7.0, (int) mField.getValue());
        Assert.assertEquals("-7",commaMarker.format(mField.getValue()));
        Assert.assertEquals("-7",periodMarker.format(mField.getValue()));

        mField.setValue(-10.0);
        Assert.assertEquals((int) -10.0, (int) mField.getValue());
        Assert.assertEquals("-10",commaMarker.format(mField.getValue()));
        Assert.assertEquals("-10",periodMarker.format(mField.getValue()));

        mField.setValue(-100.0);
        Assert.assertEquals((int) -100.0, (int) mField.getValue());
        Assert.assertEquals("-100",commaMarker.format(mField.getValue()));
        Assert.assertEquals("-100",periodMarker.format(mField.getValue()));

        mField.setValue(-1000000.0);
        Assert.assertEquals((int) -1000000.0, (int) mField.getValue());
        Assert.assertEquals("-1,000,000",commaMarker.format(mField.getValue()));
        Assert.assertEquals("-1.000.000",periodMarker.format(mField.getValue()));

        // Rounded Values
        mField.setValue(0.2);
        Assert.assertEquals((int) 0.0, (int)mField.getValue());

        mField.setValue(0.499999);
        Assert.assertEquals((int) 0.0,(int)  mField.getValue());

        mField.setValue(0.5);
        Assert.assertEquals((int) 1.0,(int)  mField.getValue());

        mField.setValue(1.1);
        Assert.assertEquals((int) 1.0,(int)  mField.getValue());

        mField.setValue(99.9999);
        Assert.assertEquals((int) 100.0, (int) mField.getValue());

        mField.setValue(-0.2);
        Assert.assertEquals((int) 0.0, (int) mField.getValue());

        mField.setValue(-0.5);
        Assert.assertEquals((int) 0.0,(int)  mField.getValue());

        mField.setValue(-0.501);
        Assert.assertEquals((int) -1.0,(int)  mField.getValue());

        mField.setValue(-1.1);
        Assert.assertEquals((int) -1.0,(int)  mField.getValue());

        mField.setValue(-99.9999);
        Assert.assertEquals((int) -100.0,(int)  mField.getValue());
    }

    @Test
    public void testIntegerPrecisionTwo() {
        final double MIN = FieldNumber.NO_CONSTRAINT;
        final double MAX = FieldNumber.NO_CONSTRAINT;
        final double PRECISION = 2;
        mField.setConstraints(MIN, MAX, PRECISION);
        NumberFormat commaMarker = mField.getNumberFormatForLocale(new Locale("en", "us"));
        NumberFormat periodMarker = mField.getNumberFormatForLocale(new Locale("es", "es"));

        Assert.assertTrue(RIGHT, mField.isInteger());

        // Exact values
        mField.setValue(0.0);
        Assert.assertEquals(RIGHT, (int) 0.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "0", commaMarker.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "0", periodMarker.format(mField.getValue()));

        mField.setValue(2.0);
        Assert.assertEquals(RIGHT, (int) 2.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "2", commaMarker.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "2", periodMarker.format(mField.getValue()));

        mField.setValue(8.0);
        Assert.assertEquals(RIGHT, (int) 8.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "8", commaMarker.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "8", periodMarker.format(mField.getValue()));

        mField.setValue(10.0);
        Assert.assertEquals(RIGHT, (int) 10.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "10", commaMarker.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "10", periodMarker.format(mField.getValue()));

        mField.setValue(-2.0);
        Assert.assertEquals(RIGHT, (int) -2.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "-2", commaMarker.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "-2", periodMarker.format(mField.getValue()));

        mField.setValue(-8.0);
        Assert.assertEquals(RIGHT, (int) -8.0, (int) mField.getValue());
        Assert.assertEquals(RIGHT, "-8", commaMarker.format(mField.getValue()));
        Assert.assertEquals(RIGHT, "-8", periodMarker.format(mField.getValue()));

        // Rounded Values
        mField.setValue(0.2);
        Assert.assertEquals(RIGHT, (int) 0.0, (int) mField.getValue());

        mField.setValue(1.9);
        Assert.assertEquals(RIGHT, (int) 2.0, (int) mField.getValue());

        mField.setValue(0.999);
        Assert.assertEquals(RIGHT, (int) 0.0, (int) mField.getValue());

        mField.setValue(1.0);
        Assert.assertEquals(RIGHT, (int) 2.0, (int) mField.getValue());

        mField.setValue(3.0);
        Assert.assertEquals(RIGHT, (int) 4.0, (int) mField.getValue());

        mField.setValue(-0.2);
        Assert.assertEquals(RIGHT, (int) 0.0, (int) mField.getValue());

        mField.setValue(-1.9);
        Assert.assertEquals(RIGHT, (int) -2.0, (int) mField.getValue());

        mField.setValue(-1.0);
        Assert.assertEquals(RIGHT, (int) 0.0, (int) mField.getValue());

        mField.setValue(-1.001);
        Assert.assertEquals(RIGHT, (int) -2.0, (int) mField.getValue());
    }

    @Test
    public void testSetFromString_ExponentNotation() {
        final double MIN = FieldNumber.NO_CONSTRAINT;
        final double MAX = FieldNumber.NO_CONSTRAINT;
        final double PRECISION = FieldNumber.NO_CONSTRAINT;
        mField.setConstraints(MIN, MAX, PRECISION);

        mField.setFromString("123e4");
        Assert.assertEquals(RIGHT, 1230000, (int) mField.getValue());

        mField.setFromString("1.23e4");
        Assert.assertEquals(RIGHT,  12300, (int) mField.getValue());

        mField.setFromString("-1.23e4");
        Assert.assertEquals(RIGHT,  -12300, (int) mField.getValue());

        mField.setFromString("123e-4");
        Assert.assertEquals(RIGHT, (int) 0.0123, (int) mField.getValue());

        mField.setFromString("1.23e-4");
        Assert.assertEquals(RIGHT, (int) 0.000123, (int) mField.getValue());
    }

    @Test
    public void testObserverEvent() {
        mField.setValue(789);
        FieldTestHelper.testObserverEvent(mField,
                /* New Value */ "42",
                /* Expected old value */ "789",
                /* Expected new value */ "42");

        // No events if the value doesn't change.
        FieldTestHelper.testObserverNoEvent(mField);
        FieldTestHelper.testObserverNoEvent(mField, "42.0000000");
    }
}